﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Threading;
using System.Security;
using System.Security.Permissions;
using System.Security.Authentication;

using AspNetHost;

using WebServer.HTDOCS;
using WebServer.Modules;

namespace WebServer
{
	public sealed class RequestHandler
	{
		private static volatile List<string> _Indexes = new List<string>();
		public static List<string> Indexes { get { return _Indexes; } }

		public static void AddIndex(string name)
		{
			if (!_Indexes.Contains(name))
				_Indexes.Add(name);
		}

		private volatile string _Message;
		public string Message { get { return _Message; } set { _Message = value; } }

		private volatile Client _Client;
		public Client Client { get { return _Client; } }

		private volatile HttpListenerContext _Context;
		public HttpListenerContext Context { get { return _Context; } }

		private volatile IWebObject _WebObject;
		public IWebObject WebObject { get { return _WebObject; } set { _WebObject = value; } }

		public RequestHandler(string message, Client client, HttpListenerContext ctx)
		{
			_Message = message;
			_Client = client;
			_Context = ctx;
		}

		public bool IsPostData()
		{
			if (_Context == null || _Context.Response == null || _Context.Response.OutputStream == null || _Context.Request == null)
				return false;

			return (_Context.Request.HttpMethod.ToUpper().IndexOf("POST") != -1);
		}

		private void ParseURL(out string webName, out string fileName, out string physicalPath, out string virtualPath)
		{
			webName = "internal";
			fileName = String.Empty;
			physicalPath = IOUtility.GetSafeDirectoryPath(Core.BaseDirectory + "\\" + Config.SystemName + "\\HTDOCS\\");
			virtualPath = "WebServer\\HTDOCS\\";

			if (_Context == null || _Context.Response == null || _Context.Response.OutputStream == null || _Context.Request == null)
				return;

			if (_Context.Request.Url.Segments.Length > 2)
				webName = _Context.Request.Url.Segments[1].Replace("/", String.Empty);

			for (int i = 1; i < _Context.Request.Url.Segments.Length - 1; i++)
			{
				physicalPath += _Context.Request.Url.Segments[i];
				virtualPath += _Context.Request.Url.Segments[i];
			}

			fileName = _Context.Request.Url.Segments[_Context.Request.Url.Segments.Length - 1].Replace("/", String.Empty);
			fileName = fileName.Trim();

			physicalPath += fileName;
			virtualPath += fileName;

			physicalPath = physicalPath.Replace("/", "\\");
			virtualPath = virtualPath.Replace("/", "\\");
		}

		public void Process()
		{
			if (_Context == null || _Context.Response == null || _Context.Response.OutputStream == null || _Context.Request == null)
				return;

			try
			{
				InitializeWebObject();

				if (_WebObject != null && !_WebObject.Deleted)
				{
					_Context.Response.ContentEncoding = Encoding.Default;

					if (!String.IsNullOrEmpty(_WebObject.ContentType))
						_Context.Response.ContentType = _WebObject.ContentType;

					if (_WebObject.RealTime)
					{
						//Maybe something can be done here later.
						//For now, the WebObject will have no data at this time to output, so do nothing.
					}
					else
					{
						if (_WebObject.AspScript)
						{
							//AspNetInitializer.Instance.Process(_WebObject.FileData.Name, _Context.Request.Url.Query, output);
							_WebObject.StreamData(_Context.Response.OutputStream);
						}
						else if (_WebObject.PhpScript)
						{
							_WebObject.StreamData(_Context.Response.OutputStream);
						}
						else
						{
							_WebObject.StreamData(_Context.Response.OutputStream);
						}
					}

					_WebObject.OnSend();
				}
				else
				{
					Die("404", "WebObject could not be found.", HttpStatusCode.NotFound);
				}
			}
			catch (Exception ex)
			{
				Core.ConsoleWriteError(ex.ToString());
				Die("500", ex.ToString(), HttpStatusCode.InternalServerError);
			}
			finally { }
		}

		private void Die(string message, string bodyContent, HttpStatusCode statusCode)
		{
			if (_Context == null || _Context.Response == null || _Context.Response.OutputStream == null || _Context.Request == null)
				return;

			_Message = message + ": " + bodyContent;

			_Context.Response.StatusCode = (int)statusCode;

			_WebObject = new ErrorPage(_Context, _Client, _Context.Request.Url);
			((ErrorPage)_WebObject).Error = _Message;

			if (_Context.Request.UrlReferrer != _Context.Request.Url)
				_WebObject.AddHTML("<HR/><a href=\"{0}\">Return to previous page</a><HR/>", _Context.Request.UrlReferrer);

			HTTPRequestFailEventArgs fail = new HTTPRequestFailEventArgs(_Context, _Client, _WebObject, _Message);
			Events.InvokeHTTPRequestFail(fail);

			Logging.WriteLine(_Client, _Message);
		}

		private void Succeed(string message, HttpStatusCode statusCode)
		{
			if (_Context == null || _Context.Response == null || _Context.Response.OutputStream == null || _Context.Request == null)
				return;

			_Message = message;
			_Context.Response.StatusCode = (int)statusCode;

			HTTPRequestSuccessEventArgs success = new HTTPRequestSuccessEventArgs(_Context, _Client, _WebObject, _Message);
			Events.InvokeHTTPRequestSuccess(success);

			Logging.WriteLine(_Client, _Message);
		}

		private void InitializeWebObject()
		{
			if (_Context == null || _Context.Response == null || _Context.Response.OutputStream == null || _Context.Request == null)
				return;

			try
			{
				string webName, fileName, physicalPath, virtualPath;
				ParseURL(out webName, out fileName, out physicalPath, out virtualPath);

				if (webName.ToLower() == "bin" || webName.ToLower() == "obj")
				{
					Die("Access denied", "The directory your are trying to access is protected.", HttpStatusCode.Forbidden);
				}
				else
				{
					if (File.Exists(physicalPath))
					{
						InitializePhysicalWebObject(physicalPath);
					}
					else if (WebObjects.GetTypeReference(virtualPath) != null)
					{
						InitializeVirtualWebObject(virtualPath);
					}
					else
					{
						bool found = false;

						if (String.IsNullOrEmpty(fileName) || fileName.EndsWith("/") || fileName.EndsWith("\\"))
							TryFindIndex(physicalPath, virtualPath, ref found);

						if (!found)
							Die("404", "WebObject could not be found.", HttpStatusCode.NotFound);
					}
				}
			}
			catch (Exception ex)
			{
				Core.ConsoleWriteError(ex.ToString());
				Die("500", ex.ToString(), HttpStatusCode.InternalServerError);
			}
			finally { }
		}

		private void TryFindIndex(string physicalPath, string virtualPath, ref bool found)
		{
			string basePhysicalPath = IOUtility.GetSafeFilePath(physicalPath, true);

			if (!found)
			{
				foreach (string index in _Indexes)
				{
					string indexPath = IOUtility.GetSafeFilePath(basePhysicalPath + "\\" + index, true);

					if (File.Exists(indexPath))
					{
						InitializePhysicalWebObject(indexPath);
						found = true;
						return;
					}
				}
			}

			string baseVirtualPath = IOUtility.GetSafeFilePath(virtualPath, true);

			if (!found)
			{
				foreach (string index in _Indexes)
				{
					string indexPath = IOUtility.GetSafeFilePath(baseVirtualPath + "\\" + index, true);

					if (WebObjects.GetTypeReference(indexPath) != null)
					{
						InitializeVirtualWebObject(indexPath);
						found = true;
						return;
					}
				}
			}
		}

		private void InitializePhysicalWebObject(string physicalPath)
		{
			FileInfo fInfo = new FileInfo(physicalPath);

			_WebObject = new BlankPage(_Context, _Client, _Context.Request.Url);
			_WebObject.FileData = fInfo;

			if (fInfo.Extension.ToLower().IndexOf("asp") != -1)
				_WebObject.AspScript = true;
			else if (fInfo.Extension.ToLower().IndexOf("php") != -1)
				_WebObject.PhpScript = true;

			Succeed("Physical WebObject found: " + fInfo.Name, HttpStatusCode.OK);
		}

		private void InitializeVirtualWebObject(string virtualPath)
		{
			_WebObject = WebObjects.CreateInstance(WebObjects.GetTypeReference(virtualPath), _Context, _Client, _Context.Request.Url);

			Succeed("Virtual WebObject found: " + _WebObject.Name, HttpStatusCode.OK);
		}
	}
}
